DP Tensor Experiment - v5

Purpose:

Vectorize v4 - will referneec v3

Conclusions:


In [126]:
import torch as th

In [307]:
max_long = 2**62

class PrivateTensor():
    
    def __init__(self, values, max_vals, min_vals, entities=None):
        
        self.values = values
        self.max_vals = max_vals
        self.min_vals = min_vals
        
        if(entities is None):
            entities = self.max_vals != self.min_vals
        
        # one hot encoding of entities in the ancestry of this tensor
        self.entities = entities
        
    def __add__(self, other):
        
        # add to a private number
        if(isinstance(other, PrivateTensor)):
            new_vals = self.values + other.values
            new_max_vals = (self.max_vals * self.entities) + (other.max_vals * other.entities)
            new_min_vals = (self.min_vals * self.entities) + (other.min_vals * other.entities)
            
        else:
            # add to a public number
            new_vals = self.values + other
            new_max_vals = self.max_vals + other
            new_min_vals = self.min_vals + other
        
        return PrivateTensor(new_vals,
                    new_max_vals,
                    new_min_vals) 
    
    def __sub__(self, other):
        
        # add to a private number
        if(isinstance(other, PrivateTensor)):
            
            new_vals = self.values - other.values
            
            # note that other.max/min values are reversed on purpose
            # because this functionality is equivalent to
            # output = self + (other * -1) and multiplication by
            # a negative number swaps the max/min values with each
            # other and flips their sign
            new_max_vals = (self.entities * self.max_vals) - (other.entities * other.min_vals)
            new_min_vals = (self.entities * self.min_vals) - (other.entities * other.max_vals)
            
        else:
            # add to a public number
            new_vals = self.values - other
            new_max_vals = self.max_vals - other
            new_min_vals = self.min_vals - other
        
        return PrivateTensor(new_vals,
                    new_max_vals,
                    new_min_vals) 
    
    def __mul__(self, other):
        
        if(isinstance(other, PrivateTensor)):
            
            new_vals = self.values * other.values
            
            new_self_max_vals = th.max(self.min_vals * other.expanded_minminvals,
                                         self.max_vals * other.expanded_maxmaxvals)
            
            new_self_min_vals = th.min(self.min_vals * other.expanded_maxmaxvals,
                                         self.max_vals * other.expanded_minminvals)
            
            
            new_other_max_vals = th.max(other.min_vals * self.expanded_minminvals,
                                          other.max_vals * self.expanded_maxmaxvals)
            
            new_other_min_vals = th.max(other.min_vals * self.expanded_maxmaxvals,
                                          other.max_vals * self.expanded_minminvals)
            
            
            entities_self_or_other = (self.entities + other.entities) > 0
            
            new_self_max_vals = (new_self_max_vals * self.entities) + ((1 - self.entities) * -max_long)
            new_other_max_vals = (new_other_max_vals * self.entities) + ((1 - other.entities) * -max_long)
            
            new_max_vals = th.max(new_self_max_vals, new_other_max_vals) * entities_self_or_other
            
            new_self_min_vals = (new_self_min_vals * self.entities) + ((1 - self.entities) * max_long)
            new_other_min_vals = (new_other_min_vals * self.entities) + ((1 - other.entities) * max_long)
            
            new_min_vals = th.min(new_self_min_vals, new_other_min_vals) * entities_self_or_other
            
        
        else:
            # add to a public number
            new_vals = self.values * other

            if(other > 0):
                new_max_vals = self.max_vals * other
                new_min_vals = self.min_vals * other
            else:
                new_min_vals = self.max_vals * other
                new_max_vals = self.min_vals * other
        
        return PrivateTensor(new_vals,
                    new_max_vals,
                    new_min_vals) 
    
    def __neg__(self):
        
        # note that new_min_vals and new_max_vals are reversed intentionally
        return PrivateTensor(-self.values,
                    -self.min_vals,
                    -self.max_vals) 

    def __truediv__(self, other):
        
        if(isinstance(other, PrivateTensor)):
            raise Exception("probably best not to do this - it's gonna be inf a lot")
            
        new_vals = self.values / other
        new_max_vals = self.max_vals / other
        new_min_vals = self.min_vals / other
        
        return PrivateTensor(new_vals,
                                new_max_vals,
                                new_min_vals)
    
    def __gt__(self, other):
        """BUG: the zero values mess this up"""
        if(isinstance(other, PrivateTensor)):
        
            new_vals = self.values > other.values
        
            # if self is bigger than the biggest possible other
            if_left = (self.min_vals > other.expanded_maxmaxvals).float() * self.entities
            
            # if self is smaller than the smallest possible other
            if_right = (self.max_vals < other.expanded_minminvals).float() * self.entities
            
            # if self doesn't overlap with other at all
            if_left_or_right = if_left + if_right # shouldn't have to check if this > 2 assuming
                                                  # other's max is > other's min
                
            # if self does overlap with other
            new_self_max_vals = 1 - if_left_or_right
            
            # can't have a threshold output less than 0
            new_self_min_vals = if_left_or_right * 0
            
            # if other is bigger than the smallest possible self
            if_left = (other.min_vals > self.expanded_maxmaxvals).float() * other.entities
            
            # if other is smaller than the smallest possible self
            if_right = (other.max_vals < self.expanded_minminvals).float() * other.entities
            
            # if other and self don't overlap
            if_left_or_right = if_left + if_right # shouldn't have to check if this > 2 assuming
                                                  # other's max is > other's min
        
            # if other and self do overlap
            new_other_max_vals = 1 - if_left_or_right
            
            # the smallest possible result is 0
            new_other_min_vals = new_self_min_vals + 0
        
            # only contribute information from entities in ancestry
            new_self_max_vals = (new_self_max_vals * self.entities) + ((1 - self.entities) * -max_long)
            new_other_max_vals = (new_other_max_vals * self.entities) + ((1 - self.entities) * -max_long)
        
            # only contribute information from entities in ancestry
            new_self_min_vals = (new_self_min_vals * self.entities) + ((1 - self.entities) * max_long)
            new_other_min_vals = (new_other_min_vals * self.entities) + ((1 - self.entities) * max_long)
            
            entities_self_or_other = ((self.entities + other.entities) > 0).float()
            
            new_max_val = th.max(new_self_max_vals, new_other_max_vals) * entities_self_or_other
            new_min_val = th.min(new_self_min_vals, new_other_min_vals) * entities_self_or_other

        else:

            new_vals = self.values > other
            
            if_left = other <= self.max_vals
            if_right = other >= self.min_vals
            if_and = if_left * if_right
            
            new_max_val = if_and
            new_min_val = new_max_val * 0
            
        return PrivateTensor(new_vals,
                             new_max_val,
                             new_min_val)
    
    def __lt__(self, other):
        
        if(isinstance(other, PrivateTensor)):
        
            result = self.values > other.values
        
            # if self is bigger than the biggest possible other
            if_left = (self.min_vals > other.expanded_maxmaxvals).float() * self.entities
            
            # if self is smaller than the smallest possible other
            if_right = (self.max_vals < other.expanded_minminvals).float() * self.entities
            
            # if self doesn't overlap with other at all
            if_left_or_right = if_left + if_right # shouldn't have to check if this > 2 assuming
                                                  # other's max is > other's min
                
            # if self does overlap with other
            new_self_max_vals = 1 - if_left_or_right
            
            # can't have a threshold output less than 0
            new_self_min_vals = if_left_or_right * 0
            
            
            # if other is bigger than the smallest possible self
            if_left = (other.min_vals > self.expanded_maxmaxvals).float() * other.entities
            
            # if other is smaller than the smallest possible self
            if_right = (other.max_vals < self.expanded_minminvals).float() * other.entities
            
            # if other and self don't overlap
            if_left_or_right = if_left + if_right # shouldn't have to check if this > 2 assuming
                                                  # other's max is > other's min
        
            # if other and self do overlap
            new_other_max_vals = 1 - if_left_or_right
            
            # the smallest possible result is 0
            new_other_min_vals = new_self_min_vals + 0
                
            # only contribute information from entities in ancestry
            new_self_max_vals = (new_self_max_vals * self.entities) + ((1 - self.entities) * -max_long)
            new_other_max_vals = (new_other_max_vals * self.entities) + ((1 - self.entities) * -max_long)
        
            # only contribute information from entities in ancestry
            new_self_min_vals = (new_self_min_vals * self.entities) + ((1 - self.entities) * max_long)
            new_other_min_vals = (new_other_min_vals * self.entities) + ((1 - self.entities) * max_long)
            
            entities_self_or_other = ((self.entities + other.entities) > 0).float()
            
            new_max_val = th.max(new_self_max_vals, new_other_max_vals) * entities_self_or_other
            new_min_val = th.min(new_self_min_vals, new_other_min_vals) * entities_self_or_other
        
        else:

            result = self.values < other
            
            if_left = other <= self.max_vals
            if_right = other >= self.min_vals
            if_and = if_left * if_right
            
            new_max_val = if_and
            new_min_val = new_max_val * 0
            
        return PrivateTensor(result,
                             new_max_val,
                             new_min_val)

    def clamp_min(self, other):
        
        if(isinstance(other, PrivateTensor)):
            raise Exception("Not implemented yet")
        
        new_min_val = self.min_vals.clamp_min(other)
            
        return PrivateTensor(self.values.clamp_min(other),
                                self.max_vals,
                                new_min_val)
    
    def clamp_max(self, other):
        
        if(isinstance(other, PrivateTensor)):
            raise Exception("Not implemented yet")
        
        entities = self.entities
        
        new_max_val = self.max_vals.clamp_max(other)
                
        return PrivateTensor(self.values.clamp_max(other),
                                new_max_val,
                                self.min_vals)
    
    @property
    def maxmaxvals(self):
        """This returns the maximum possible value over all entities"""
        
        return (self.max_vals * self.entities).max(1)[0]
    
    @property
    def expanded_maxmaxvals(self):
        return self.maxmaxvals.unsqueeze(1).expand(self.max_vals.shape)
    
    @property
    def minminvals(self):
        """This returns the minimum possible values over all entities"""

        return (self.min_vals * self.entities).min(1)[0]
    
    @property
    def expanded_minminvals(self):
        return self.minminvals.unsqueeze(1).expand(self.min_vals.shape)
    
    @property
    def sensitivity(self):
        return (self.max_vals - self.min_vals).max(1)[0]
    
    @property
    def entities(self):
        return self._entities
        
    @entities.setter
    def entities(self, x):
        self._entities = x.float()
        
    def hard_sigmoid(self):
        return self.min(1).max(0)
    
    def hard_sigmoid_deriv(self, leak=0.01):
        return ((self < 1) * (self > 0)) + (self < 0) * leak - (self > 1) * leak

In [308]:
data = th.tensor([2.,0,1,0,0,0])
max_vals = th.tensor([[3.,1,0,0,0], [0,1,0,0,0], [0,0,1,0,0], [0,0,0,0,0], [0,0,0,0,1], [0,0,0,0,1]])
min_vals = th.tensor([[2.,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0], [0,0,0,0,0]])
x = PrivateTensor(values=data, 
                  max_vals=max_vals, 
                  min_vals=min_vals)

In [309]:
y = x.clamp_min(1)

In [310]:
y.values


Out[310]:
tensor([2., 1., 1., 1., 1., 1.])

In [311]:
y.sensitivity


Out[311]:
tensor([ 1.,  0.,  0., -1.,  0.,  0.])

In [314]:
x.values.clamp_max(1)


Out[314]:
tensor([1., 0., 1., 0., 0., 0.])

In [315]:
y.values


Out[315]:
tensor([2., 1., 1., 1., 1., 1.])

In [ ]:


In [119]:
from collections import Counter
import numpy as np

class PrivateNumber():
    
    def __init__(self, value, max_val, min_val):
        self.value = value
        self.max_val = max_val
        self.min_val = min_val
        
    def __add__(self, other):
        
        # add to a private number
        
        if(isinstance(other, PrivateNumber)):

            entities = self.entities.union(other.entities)
            
            new_val = self.value + other.value

            entities = set(self.max_val.keys()).union(set(other.max_val.keys()))

            new_max_val = {}
            new_min_val = {}
            for entity in entities:
                
                self_max = self.max_val[entity] if entity in self.max_val else 0
                other_max = other.max_val[entity] if entity in other.max_val else 0
                
                self_min = self.min_val[entity] if entity in self.min_val else 0
                other_min = other.min_val[entity] if entity in other.min_val else 0
                
                new_max_val[entity] = self_max + other_max
                new_min_val[entity] = self_min + other_min

        else:

            entities = self.entities

            # add to a public number
            new_val = self.value + other
            
            new_max_val = {}
            new_min_val = {}
            for entity in entities:
                new_max_val[entity] = self.max_val[entity] + other
                new_min_val[entity] = self.min_val[entity] + other

        return PrivateNumber(new_val,
                             new_max_val,
                             new_min_val)
    
    def __mul__(self, other):
        
        if(isinstance(other, PrivateNumber)):
        
            new_self_max_val = {}
            new_self_min_val = {}
            for entity in self.entities:
                
                # the biggest positive number this entity could contribute is when
                # it is multiplied by the largest value of the same sign from other
                new_self_max_val[entity] = max(self.min_val[entity] * other.xmin, 
                                               self.max_val[entity] * other.xmax)
                
                # the smallest negative number this entity could contribute is when
                # it is multiplied by the largest value of the opposite sign from other
                new_self_min_val[entity] = min(self.min_val[entity] * other.xmax,
                                               self.max_val[entity] * other.xmin)
                
            new_other_max_val = {}
            new_other_min_val = {}
            for entity in other.entities:
                
                # the biggest positive number this entity could contribute is when
                # it is multiplied by the largest value of the same sign from other
                new_other_max_val[entity] = max(other.min_val[entity] * self.xmin, 
                                                other.max_val[entity] * self.xmax)
                
                # the smallest negative number this entity could contribute is when
                # it is multiplied by the largest value of the opposite sign from other
                new_other_min_val[entity] = min(other.min_val[entity] * self.xmax,
                                                other.max_val[entity] * self.xmin)
                
            new_max_val = {}
            new_min_val = {}
            
            for entity in self.entities:
                left = new_self_max_val[entity] if entity in new_self_max_val else -(2**32)
                right = new_other_max_val[entity] if entity in new_other_max_val else -(2**32)
                
                new_max_val[entity] = max(left, right)
                
                left = new_self_min_val[entity] if entity in new_self_min_val else 2**32
                right = new_other_min_val[entity] if entity in new_other_min_val else 2**32
                
                new_min_val[entity] = min(left, right)

            return PrivateNumber(self.value * other.value,
                                    new_max_val,
                                    new_min_val)
            
    
        new_max_val = {}
        for entity in self.entities:
            new_max_val[entity] = self.max_val[entity] * other

        new_min_val = {}
        for entity in self.entities:
            new_min_val[entity] = self.min_val[entity] * other
        
        if(other > 0):
            return PrivateNumber(self.value * other,
                                    new_max_val,
                                    new_min_val)
        else:
            return PrivateNumber(self.value * other,
                                    new_min_val,                                 
                                    new_max_val)
    
    def __neg__(self):
        return self * -1
    
    def __sub__(self, other):
        return self + (-other)

    def __gt__(self, other):

        if(isinstance(other, PrivateNumber)):
        
            new_self_max_val = {}
            new_self_min_val = {}
            for entity in self.entities:
                
                if not (self.min_val[entity] > other.xmax or self.max_val[entity] < other.xmin):
                    new_self_max_val[entity] = 1
                else:
                    new_self_max_val[entity] = 0
                
                new_self_min_val[entity] = 0
                
            new_other_max_val = {}
            new_other_min_val = {}
            for entity in other.entities:
                
                if not (other.min_val[entity] > self.xmax or other.max_val[entity] < self.xmin):
                    new_other_max_val[entity] = 1
                else:
                    new_other_max_val[entity] = 0
                    
                new_other_min_val[entity] = 0
                
            new_max_val = {}
            new_min_val = {}
            
            entities = self.entities.union(other.entities)
                
            for entity in entities:
                
                new_self_max = new_self_max_val[entity] if entity in new_self_max_val else -999999999
                new_other_max = new_other_max_val[entity] if entity in new_other_max_val else -999999999
                
                new_self_min = new_self_min_val[entity] if entity in new_self_min_val else 99999999
                new_other_min = new_other_min_val[entity] if entity in new_other_min_val else 99999999
                
                new_max_val[entity] = max(new_self_max, new_other_max)
                new_min_val[entity] = min(new_self_min, new_other_min)

            result = int(self.value > other.value)
        else:

            entities = self.entities.union(other.entities)

            new_max_val = {}
            new_min_val = {}
            for entity in entities:

                new_min_val[entity] = 0

                if(other <= self.max_val[entity] and other >= self.min_val[entity]):    
                    new_max_val[entity] = 1
                else:
                    new_max_val[entity] = 0

            result = int(self.value > other)
            
        return PrivateNumber(result,
                                new_max_val,
                                new_min_val)
    
    def __lt__(self, other):
        
        if(isinstance(other, PrivateNumber)):
        
            entities = self.entities.union(other.entities)
        
            new_self_max_val = {}
            new_self_min_val = {}
            
            for entity in self.entities:
                
                if not (self.min_val[entity] > other.xmax or self.max_val[entity] < other.xmin):
                    new_self_max_val[entity] = 1
                else:
                    new_self_max_val[entity] = 0
                
                new_self_min_val[entity] = 0
                
            new_other_max_val = {}
            new_other_min_val = {}
            
            for entity in other.entities:
                
                if not (other.min_val[entity] > self.xmax or other.max_val[entity] < self.xmin):
                    new_other_max_val[entity] = 1
                else:
                    new_other_max_val[entity] = 0
                    
                new_other_min_val[entity] = 0
                
            new_max_val = {}
            new_min_val = {}
            
            entities = self.entities.union(other.entities)
                
            for entity in entities:
                
                new_self_max = new_self_max_val[entity] if entity in new_self_max_val else -999999999
                new_other_max = new_other_max_val[entity] if entity in new_other_max_val else -999999999
                
                new_self_min = new_self_min_val[entity] if entity in new_self_min_val else 99999999
                new_other_min = new_other_min_val[entity] if entity in new_other_min_val else 99999999
                
                new_max_val[entity] = max(new_self_max, new_other_max)
                new_min_val[entity] = min(new_self_min, new_other_min)

            result = int(self.value < other.value)
        
        else:

            entities = self.entities

            new_max_val = {}
            new_min_val = {}
            for entity in entities:

                new_min_val[entity] = 0

                if(other <= self.max_val[entity] and other >= self.min_val[entity]):    
                    new_max_val[entity] = 1
                else:
                    new_max_val[entity] = 0

            result = int(self.value < other)
        
        return PrivateNumber(result,
                             new_max_val,
                             new_min_val)
    
    def max(self, other):
        
        if(isinstance(other, PrivateNumber)):
            raise Exception("Not implemented yet")
        
        entities = self.entities
        
        new_min_val = {}
        for entity in entities:
            new_min_val[entity] = max(self.min_val[entity], other)
            
        return PrivateNumber(max(self.value, other),
                                self.max_val,
                                new_min_val)
    
    def min(self, other):
        
        if(isinstance(other, PrivateNumber)):
            raise Exception("Not implemented yet")
        
        entities = self.entities
        
        new_max_val = {}
        for entity in entities:
            new_max_val[entity] = min(self.max_val[entity], other)
                
        return PrivateNumber(min(self.value, other),
                                new_max_val,
                                self.min_val)
    
    def __repr__(self):
        return str(self.value) + " " + str(self.max_val) + " " + str(self.min_val)
    
    def hard_sigmoid(self):
        return self.min(1).max(0)
    
    def hard_sigmoid_deriv(self):
        return ((self < 1) * (self > 0)) + (self < 0) * 0.01 - (self > 1) * 0.01
    
    @property
    def xmin(self):
        items = list(self.min_val.items())
        out = items[0][1]
        for k,v in items[1:]:
            if(v < out):
                out = v
        return out
    
    @property
    def xmax(self):
        items = list(self.max_val.items())
        out = items[0][1]
        for k,v in items[1:]:
            if(v > out):
                out = v
        return out
    
    @property
    def entities(self):
        return set(self.max_val.keys())
    
    @property
    def sensitivity(self):
        sens = Counter()
        for entity, value in self.max_val.items():
            sens[entity] = value - self.min_val[entity]
        return sens.most_common()[0][1]

In [120]:
x = PrivateNumber(0.5,{"bob":4, "amos":3},{"bob":3, "amos":2})
y = PrivateNumber(1,{"bob":1},{"bob":-1})
z = PrivateNumber(-0.5,{"sue":2},{"sue":-1})

In [121]:
a = y < z

In [122]:
a.sensitivity


Out[122]:
1

In [ ]:


In [ ]:


In [ ]:


In [ ]:


In [30]:
from collections import Counter
import numpy as np
class PrivateNumber():
    
    def __init__(self, value, max_val, min_val):
        self.value = value
        self.max_val = max_val
        self.min_val = min_val
        
    def __add__(self, other):
        
        # add to a private number
        
        if(isinstance(other, PrivateNumber)):

            entities = self.entities.union(other.entities)
            
            new_val = self.value + other.value

            entities = set(self.max_val.keys()).union(set(other.max_val.keys()))

            new_max_val = Counter()
            new_min_val = Counter()            
            for entity in entities:
                new_max_val[entity] = self.max_val[entity] + other.max_val[entity]
                new_min_val[entity] = self.min_val[entity] + other.min_val[entity]

            return PrivateNumber(self.value + other.value,
                                new_max_val,
                                new_min_val)
        
        entities = self.entities
        
        # add to a public number
        
        new_max_val = Counter()
        new_min_val = Counter()        
        for entity in entities:
            new_max_val[entity] = self.max_val[entity] + other
            new_min_val[entity] = self.min_val[entity] + other
        
        return PrivateNumber(self.value + other,
                                new_max_val,
                                new_min_val)

    def __mul__(self, other):
        
        if(isinstance(other, PrivateNumber)):
        
            entities = self.entities.union(other.entities)
        
            new_self_max_val = Counter()
            new_self_min_val = Counter()            
            for entity in entities:
                
                # the biggest positive number this entity could contribute is when
                # it is multiplied by the largest value of the same sign from other
                new_self_max_val[entity] = max(self.min_val[entity] * other.xmin, 
                                               self.max_val[entity] * other.xmax)
                
                # the smallest negative number this entity could contribute is when
                # it is multiplied by the largest value of the opposite sign from other
                new_self_min_val[entity] = min(self.min_val[entity] * other.xmax,
                                               self.max_val[entity] * other.xmin)
                
            new_other_max_val = Counter()
            new_other_min_val = Counter()            
            for entity in entities:
                
                # the biggest positive number this entity could contribute is when
                # it is multiplied by the largest value of the same sign from other
                new_other_max_val[entity] = max(other.min_val[entity] * self.xmin, 
                                                other.max_val[entity] * self.xmax)
                
                # the smallest negative number this entity could contribute is when
                # it is multiplied by the largest value of the opposite sign from other
                new_other_min_val[entity] = min(other.min_val[entity] * self.xmax,
                                                other.max_val[entity] * self.xmin)
                
            new_max_val = Counter()
            new_min_val = Counter()
            
            for entity in entities:
                new_max_val[entity] = max(new_self_max_val[entity], new_other_max_val[entity])
                new_min_val[entity] = min(new_self_min_val[entity], new_other_min_val[entity])

            return PrivateNumber(self.value * other.value,
                                    new_max_val,
                                    new_min_val)
        
        entities = self.entities
        
        new_max_val = Counter()
        for entity in entities:
            new_max_val[entity] = self.max_val[entity] * other

        new_min_val = Counter()
        for entity in entities:
            new_min_val[entity] = self.min_val[entity] * other
        
        if(other > 0):
            return PrivateNumber(self.value * other,
                                    new_max_val,
                                    new_min_val)
        else:
            return PrivateNumber(self.value * other,
                                    new_min_val,                                 
                                    new_max_val)
    
    def __sub__(self, other):
        return self + (-other)
    
    def __mul__(self, other):
        
        if(isinstance(other, PrivateNumber)):
        
            entities = self.entities.union(other.entities)
        
            new_self_max_val = Counter()
            new_self_min_val = Counter()            
            for entity in entities:
                
                # the biggest positive number this entity could contribute is when
                # it is multiplied by the largest value of the same sign from other
                new_self_max_val[entity] = max(self.min_val[entity] * other.xmin, 
                                               self.max_val[entity] * other.xmax)
                
                # the smallest negative number this entity could contribute is when
                # it is multiplied by the largest value of the opposite sign from other
                new_self_min_val[entity] = min(self.min_val[entity] * other.xmax,
                                               self.max_val[entity] * other.xmin)
                
            new_other_max_val = Counter()
            new_other_min_val = Counter()            
            for entity in entities:
                
                # the biggest positive number this entity could contribute is when
                # it is multiplied by the largest value of the same sign from other
                new_other_max_val[entity] = max(other.min_val[entity] * self.xmin, 
                                                other.max_val[entity] * self.xmax)
                
                # the smallest negative number this entity could contribute is when
                # it is multiplied by the largest value of the opposite sign from other
                new_other_min_val[entity] = min(other.min_val[entity] * self.xmax,
                                                other.max_val[entity] * self.xmin)
                
            new_max_val = Counter()
            new_min_val = Counter()
            
            for entity in entities:
                new_max_val[entity] = max(new_self_max_val[entity], new_other_max_val[entity])
                new_min_val[entity] = min(new_self_min_val[entity], new_other_min_val[entity])

            return PrivateNumber(self.value * other.value,
                                    new_max_val,
                                    new_min_val)
        
        entities = self.entities
        
        new_max_val = Counter()
        for entity in entities:
            new_max_val[entity] = self.max_val[entity] * other

        new_min_val = Counter()
        for entity in entities:
            new_min_val[entity] = self.min_val[entity] * other
        
        if(other > 0):
            return PrivateNumber(self.value * other,
                                    new_max_val,
                                    new_min_val)
        else:
            return PrivateNumber(self.value * other,
                                    new_min_val,                                 
                                    new_max_val)
    
    def __truediv__(self, other):
        
        if(isinstance(other, PrivateNumber)):
            raise Exception("probably best not to do this - it's gonna be inf a lot")
            
        entities = self.entities
        
        new_max_val = Counter()
        for entity in entities:
            new_max_val[entity] = self.max_val[entity] / other

        new_min_val = Counter()
        for entity in entities:
            new_min_val[entity] = self.min_val[entity] / other
        
        return PrivateNumber(self.value / other,
                                new_max_val,
                                new_min_val)

    def __gt__(self, other):
        """BUG!: Counter() defaults to 0"""
        if(isinstance(other, PrivateNumber)):
        
            entities = self.entities.union(other.entities)
        
            new_self_max_val = Counter()
            new_self_min_val = Counter()            
            for entity in entities:
                
                if not (self.min_val[entity] > other.xmax or self.max_val[entity] < other.xmin):
                    new_self_max_val[entity] = 1
                else:
                    new_self_max_val[entity] = 0
                
                new_self_min_val[entity] = 0
                
            new_other_max_val = Counter()
            new_other_min_val = Counter()            
            for entity in entities:
                
                if not (other.min_val[entity] > self.xmax or other.max_val[entity] < self.xmin):
                    new_other_max_val[entity] = 1
                else:
                    new_other_max_val[entity] = 0
                    
                new_other_min_val[entity] = 0
                
            new_max_val = Counter()
            new_min_val = Counter()
            
            for entity in entities:
                new_max_val[entity] = max(new_self_max_val[entity], new_other_max_val[entity])
                new_min_val[entity] = min(new_self_min_val[entity], new_other_min_val[entity])

            return PrivateNumber(int(self.value > other.value),
                                    new_max_val,
                                    new_min_val)
        
        entities = self.entities
        
        new_max_val = Counter()
        new_min_val = Counter()
        for entity in entities:
            
            new_min_val[entity] = 0
            
            if(other <= self.max_val[entity] and other >= self.min_val[entity]):    
                new_max_val[entity] = 1
            else:
                new_max_val[entity] = 0

        return PrivateNumber(int(self.value > other),
                                new_max_val,
                                new_min_val)
    

    def __lt__(self, other):
        """BUG!: Counter() defaults to 0"""
        if(isinstance(other, PrivateNumber)):
        
            entities = self.entities.union(other.entities)
        
            new_self_max_val = Counter()
            new_self_min_val = Counter()            
            for entity in entities:
                
                if not (self.min_val[entity] > other.xmax or self.max_val[entity] < other.xmin):
                    new_self_max_val[entity] = 1
                else:
                    new_self_max_val[entity] = 0
                
                new_self_min_val[entity] = 0
                
            new_other_max_val = Counter()
            new_other_min_val = Counter()            
            for entity in entities:
                
                if not (other.min_val[entity] > self.xmax or other.max_val[entity] < self.xmin):
                    new_other_max_val[entity] = 1
                else:
                    new_other_max_val[entity] = 0
                    
                new_other_min_val[entity] = 0
                
            new_max_val = Counter()
            new_min_val = Counter()
            
            for entity in entities:
                new_max_val[entity] = max(new_self_max_val[entity], new_other_max_val[entity])
                new_min_val[entity] = min(new_self_min_val[entity], new_other_min_val[entity])

            return PrivateNumber(int(self.value < other.value),
                                    new_max_val,
                                    new_min_val)
        
        entities = self.entities
        
        new_max_val = Counter()
        new_min_val = Counter()
        for entity in entities:
            
            new_min_val[entity] = 0
            
            if(other <= self.max_val[entity] and other >= self.min_val[entity]):    
                new_max_val[entity] = 1
            else:
                new_max_val[entity] = 0

        return PrivateNumber(int(self.value < other),
                                new_max_val,
                                new_min_val)
    
    def __neg__(self):
        return self * -1
    
    def max(self, other):
        
        if(isinstance(other, PrivateNumber)):
            raise Exception("Not implemented yet")
        
        entities = self.entities
        
        new_min_val = Counter()
        for entity in entities:
            new_min_val[entity] = max(self.min_val[entity], other)
            
        return PrivateNumber(max(self.value, other),
                                self.max_val,
                                new_min_val)
    
    def min(self, other):
        
        if(isinstance(other, PrivateNumber)):
            raise Exception("Not implemented yet")
        
        entities = self.entities
        
        new_max_val = Counter()
        for entity in entities:
            new_max_val[entity] = min(self.max_val[entity], other)
                
        return PrivateNumber(min(self.value, other),
                                new_max_val,
                                self.min_val)
    
    def hard_sigmoid(self):
        return self.min(1).max(0)
    
    def hard_sigmoid_deriv(self):
        return ((self < 1) * (self > 0)) + (self < 0) * 0.01 - (self > 1) * 0.01
        
    def __repr__(self):
        return str(self.value) + " " + str(self.max_val) + " " + str(self.min_val)
    
    @property
    def xmin(self):
        return self.min_val.most_common(len(self.min_val))[-1][1]
    
    @property
    def xmax(self):
        return self.max_val.most_common(1)[0][1]
    
    @property
    def entities(self):
        return set(self.max_val.keys())
    
    @property
    def sensitivity(self):
        sens = Counter()
        for entity, value in self.max_val.items():
            sens[entity] = value - self.min_val[entity]
        return sens.most_common()[0][1]
    
x = PrivateNumber(0.5,Counter({"bob":4, "amos":3}),Counter({"bob":3, "amos":2}))
y = PrivateNumber(1,Counter({"bob":1}),Counter({"bob":-1}))
z = PrivateNumber(-0.5,Counter({"sue":2}),Counter({"sue":-1}))

In [31]:
a = x > y

In [34]:
a.sensitivity


Out[34]:
0

In [ ]:


In [2]:
a = x + y

In [4]:
b = a * z

In [6]:
b


Out[6]:
-1.0 Counter({'sue': 8, 'bob': 8, 'amos': 6}) Counter({'amos': -3, 'sue': -6, 'bob': -6})

In [ ]:


In [2]:
# class PrivacyAccountant():
    
#     def __init__(self, default_budget = 0.1):
        
#         self.entity2epsilon = {}
#         self.entity2id = {}
#         self.default_budget = default_budget
        
#     def add_entity(self, entity_id, budget=None):
#         """Add another entity to the system to be tracked.
        
#         Args:
#             entity_id: a string or other unique identifier of the entity
#             budget: the epsilon level defining this user's privacy budget
#         """
        
#         if(budget is None):
#             budget = self.default_budget
        
#         self.entity2id[entity_id] = len(self.entity2id)
#         self.entity2epsilon[self.entity2id[entity_id]] = budget
        
        
# accountant = PrivacyAccountant()

# class DPTensor():
    
#     def __init__(self, data, entities, max_values=None, min_values=None):
        
#         assert data.shape == entities.shape#[0:-1]

#         self.data = data
#         self.entities = entities
        
#         if max_values is None:
#             max_values = np.inf + np.zeros_like(self.data)
            
#         assert max_values.shape == data.shape
#         self.max_values = max_values    
        
#         if min_values is None:
#             min_values = -np.inf + np.zeros_like(self.data)            
            
#         assert min_values.shape == data.shape            
#         self.min_values = min_values

#     def sum(self, dim=0):
        
#         _new_data = self.data.sum(dim)
        
#         return _new_data
    
#     @property
#     def sensitivity(self):
#         return self.max_values - self.min_values

In [163]:
# results, tags = grid.search("diabetes","#data", verbose=False)
# dataset = results['alice'][0][0:5][:,0:4]
# n_ent = dataset.shape[0]
# n_classes = dataset.shape[1]

# for i in range(n_ent):
#     accountant.add_entity("Diabetes Patient #" + str(i))
    
# d2 = dataset.clone().get()
# entities = th.arange(0,n_ent).view(-1,1).expand(n_ent,n_classes)#.unsqueeze(2)
# db = DPTensor(data=d2, 
#               entities=entities, 
#               max_values=d2.max(0)[0].expand(n_ent,n_classes), 
#               min_values=d2.min(0)[0].expand(n_ent,n_classes))

In [ ]:


In [ ]: